#include "agentsocket.h"
#include <iostream>
#include <string>
#include <sstream>
#include <math.h>
#include "pstream.h"
#include <boost/regex.hpp>

/* This class get various system variables throw the libstatgrab and waiting
  for requests from the server */

AgentSocket::AgentSocket(ISocketHandler& h, Data *d) : TcpSocket(h, d)
, m_countOfServices(0)
, m_services(0)
, m_host_info(0)
, m_disk_array(0)
, m_data(0)
, m_eol("aaa")
, m_pathToService(0)
, m_config(0)
, m_is_check_fs(0)
, m_is_check_ps(0)
, m_is_check_mem(0)
{
        if (sg_init() || !d)
        {
                std::cout << sg_str_error(sg_error()) << std::endl;
                exit;
        }
        else
        {
                m_data = d;
                m_disk_array = m_data->getDiskArray();
                m_config = m_data->getConfig();
                this->loadConfig();

                sg_drop_privileges();
                if (!m_data->isInitDisks())
                {
                        this->initDisks();
                        m_data->setInitDisks();
                }
                else exit;
                m_host_info = sg_get_host_info();
                SetLineProtocol();

        }
}

AgentSocket::~AgentSocket()
{
}

void AgentSocket::loadConfig()
{
        if (m_config)
        {

                m_pathToService = m_config->getServicePath();
                m_services = m_config->getServices();
                m_fs_threshold = *m_config->getFsThreshold()* 1000 * 1000;
                m_load_threshold = *m_config->getLaThreshold();
                m_memory_threshold = *m_config->getFreeMemThreshold();

                m_alerts = m_config->getAlerts();
                if (m_alerts)
                {
                        list<std::string> ::const_iterator iter;
                        for (iter = m_alerts->begin(); iter != m_alerts->end(); ++iter)
                                if (*iter == "fs")
                                        m_is_check_fs = true;
                                else if ((*iter == "ps"))
                                        m_is_check_ps = true;
                                else if ((*iter == "mem"))
                                        m_is_check_mem = true;
                }
        }

}

void AgentSocket::OnLine(const std::string& cmd)
{
        const std::string toAll = "0x0";
        boost::regex const kill_matcher("^kl[[:alnum:]]{1,255}");
        boost::regex const srv_matcher("^srv[[:alnum:]]{1,255}\\:[[:alnum:]\\-]{4,12}$");
        if (boost::regex_match(cmd, srv_matcher))
        {
                uint pos = cmd.find(":");
                string command = cmd.substr(3, pos - 3);
                string action = cmd.substr(pos + 1, cmd.length());
                Send(ctrlService(command, action) + "\n\n");
        }
        else if (boost::regex_match(cmd, kill_matcher))
                Send(killProcess(cmd.substr(2, cmd.length())) + "\n\n");
        else if (cmd == "tst")
        {
                bool flag = true;
                std::string err = checkProcesses();
                if (!err.empty())
                {
                        flag = false;
                        Send(toAll + "\n\n");
                        Send(err + "\n\n");
                }
                if (m_is_check_fs)
                {
                        err.clear();
                        err = getFsStat(true);
                        if (!err.empty())
                        {
                                flag = false;
                                Send(toAll + "\n\n");
                                Send(err + "\n\n");
                        }
                }
                if (m_is_check_ps)
                {
                        err.clear();
                        err = getLoadStat(true);
                        if (!err.empty())
                        {
                                flag = false;
                                Send(toAll + "\n\n");
                                Send(err + "\n\n");
                        }
                }
                if (m_is_check_mem)
                {
                        err.clear();
                        err = getMemStat(true);
                        if (!err.empty())
                        {
                                flag = false;
                                Send(toAll + "\n\n");
                                Send(err + "\n\n");
                        }
                }
                Send("nil\n\n");
        }
        else if (cmd == "uptime")
        {
                Send(getUptime() + "\n\n");
        }
        else if (cmd == "processes")
        {
                Send(getProcesses() + "\n\n");
        }
        else if (cmd == "hostname")
        {
                Send(getHostname() + "\n\n");
        }
        else if (cmd == "osname")
        {
                Send(getOsName() + "\n\n");
        }
        else if (cmd == "osver")
        {
                Send(getOsVersion() + "\n\n");
        }
        else if (cmd == "cpu")
        {
                Send(getCpuStat() + "\n\n");
        }
        else if (cmd == "fs")
        {
                Send(getFsStat(false) + "\n\n");
        }
        else if (cmd == "load")
        {
                Send(getLoadStat(false) + "\n\n");
        }
        else if (cmd == "mem")
        {
                Send(getMemStat(false) + "\n\n");
        }
        else if (cmd == "net")
        {
                Send(getNetStat() + "\n\n");
        }
        else if (cmd == "usr")
        {
                Send(getUsersStat() + "\n\n");
        }
        else
        {
                Send("command not found\n\n");
        }


}

const std::string AgentSocket::getUptime()
{
        if (m_host_info)
        {
                uint time_sec = m_host_info->uptime;
                if (time_sec > 3600 * 24 * 30)
                        return toStr((int) time_sec / (3600 * 24 * 30)) + " months";
                if (time_sec > 3600 * 24)
                        return toStr((int) time_sec / (3600 * 24)) + " days";
                if (time_sec > 3600)
                        return toStr((int) time_sec / 3600) + " hours";
                if (time_sec > 60)
                        return toStr((int) time_sec / 60) + " minutes";
                return toStr((int) time_sec) + " seconds";
        }
        return 0;
}

const std::string AgentSocket::toStr(int i)
{
        std::stringstream out;
        out << i;
        return out.str();
}

const std::string AgentSocket::toStr(float i)
{
        std::stringstream out;
        out << i;
        return out.str();
}

const std::string AgentSocket::toStr(double i)
{
        std::stringstream out;
        out << i;
        return out.str();
}

const std::string AgentSocket::toStr(long long i)
{
        char capacity;
        float size = 0.0;
        std::stringstream out;
        if (i > pow(1024, 4))
        {
                out.precision(6);
                size = i / pow(1024, 4);
                capacity = 'T';
        }
        else if (i > pow(1024, 3))
        {
                out.precision(5);
                size = i / pow(1024, 3);
                capacity = 'G';
        }
        else if (i > pow(1024, 2))
        {
                out.precision(4);
                size = i / pow(1024, 2);
                capacity = 'M';
        }
        else if (i > 1024)
        {
                out.precision(3);
                size = i / 1024;
                capacity = 'K';
        }
        out << size;
        return out.str() + capacity;
}

const std::string AgentSocket::getCpuStat()
{
        sg_cpu_percents * cpu_stats;
        cpu_stats = sg_get_cpu_percents();
        const std::string result = m_eol + "Idle: " + toStr(cpu_stats->idle) + "%" +
                m_eol + "User: " + toStr(cpu_stats->user) + "%" +
                m_eol + "Kernel: " + toStr(cpu_stats->kernel) + "%" +
                m_eol + "IOWait: " + toStr(cpu_stats->iowait) + "%" +
                m_eol + "Swap: " + toStr(cpu_stats->swap) + "%";

        return result;
}

const std::string AgentSocket::getNetStat()
{
        int entries;
        std::string result;
        std::string duplex;
        sg_network_iface_stats * ifstat;
        ifstat = sg_get_network_iface_stats(&entries);
        for (int count = 0; count < entries; ++count)
        {
                switch (ifstat->duplex)
                {
                case 0://SG_IFACE_DUPLEX_FULL:
                        duplex = "full";
                        break;
                case 1://SG_IFACE_DUPLEX_HALF:
                        duplex = "half";
                        break;
                default:
                        duplex = "unknown";
                        break;

                }

                result += m_eol + "" + (std::string)ifstat->interface_name
                        + "\tduplex: " + duplex + "\tspeed:"
                        + toStr(ifstat->speed) + "\tlink: " + toStr(ifstat->up) + m_eol + "";
                ifstat++;
        }
        return result;
}

const std::string AgentSocket::getUsersStat()
{
        using namespace std;
        sg_user_stats *user_stats = sg_get_user_stats();
        std::string result;
        result = m_eol + (string) user_stats->name_list;
        uint pos = result.find(" "), end = std::string::npos;
        while (pos != end)
        {
                result.replace(pos, 1, m_eol.c_str(), 3);
                pos = result.find(" ");
        }
        return result;
}

const std::string AgentSocket::getProcesses()
{
        int v;
        //  int * a = &v;
        std::string processes;
        sg_process_stats *stat = sg_get_process_stats(&v);
        for (int i = 0; i < v; i++)
        {
                processes += m_eol + (std::string)stat->process_name;
                stat++;
        }
        return processes;

}

const std::string AgentSocket::killProcess(const std::string &str)
{
        redi::ipstream in("killall -9 " + str);
        std::string buf, result;
        while (in >> buf)
        {
                result += buf;
        }
        if (result.empty())
                result = "done";
        return result;
}

const std::string AgentSocket::ctrlService(const std::string &service, const std::string &action) const
{
        std::string command = *m_pathToService + " " + service + " " + action;
        redi::ipstream in(command);
        std::string buf, result;
        while (in >> buf)
        {
                result.append(buf);
                result.append(" ");
        }
        if (result.empty())
                result = "done";
        in.close();
        return result;
}

const std::string AgentSocket::checkProcesses()
{
        std::string err = "";
        if (m_services)
        {
                sg_process_stats *stat, *buf_stat;

                int v;
                int * a = &v;
                bool is_find, state;
                stat = sg_get_process_stats(a);
                std::map < std::string, bool> ::iterator iter;
                for (iter = m_services->begin(); iter != m_services->end(); ++iter)
                {
                        is_find = false;
                        buf_stat = stat;
                        state = (*iter).second;
                        for (int i = 0; i < v; i++)
                        {
                                if ((*iter).first == buf_stat->process_name)
                                {
                                        is_find = true;
                                        break;
                                }
                                buf_stat++;
                        }

                        if (is_find && !state)
                        {
                                err += this->getHostname() + ": Process *" + (*iter).first + "* was changed state to start" + m_eol;
                                (*iter).second = true;
                        }

                        else if (!is_find && state)
                        {
                                err += this->getHostname() + ": Process *" + (*iter).first + "* was changed state to stop" + m_eol;
                                (*iter).second = false;

                        }
                }
        }
        return err;
}

void AgentSocket::initDisks()
{
        if (m_disk_array)
        {
                int entries;
                sg_fs_stats * fs_stat;
                fs_stat = sg_get_fs_stats(&entries);
                for (int count = 0; count < entries; ++count)
                {
                        m_disk_array->insert(std::make_pair((std::string)fs_stat->device_name, true));
                        fs_stat++;
                }
        }

}

const std::string AgentSocket::getFsStat(bool is_checked)
{
        bool flag = is_checked;
        std::string result;
        int entries;
        sg_fs_stats * fs_stat;
        fs_stat = sg_get_fs_stats(&entries);
        for (int count = 0; count < entries; ++count)
        {
                std::string devname = (std::string)fs_stat->device_name;
                if (flag)
                {
                        if (fs_stat->avail < m_fs_threshold && (*m_disk_array)[devname] == true)
                        {
                                (*m_disk_array)[devname] = false;
                                result += this->getHostname() + ": *Low free disk space*:\t" +
                                        (std::string)fs_stat->mnt_point + "\t" +
                                        "*free:* " + toStr(fs_stat->avail) + m_eol;
                        }

                        else if (fs_stat->avail > m_fs_threshold && (*m_disk_array)[devname] == false)
                        {
                                (*m_disk_array)[devname] = true;
                        }
                }
                else
                {
                        result += m_eol + "*" + devname + "\t" +
                                (std::string)fs_stat->fs_type + "\t" +
                                (std::string)fs_stat->mnt_point + "*\t" +
                                m_eol + "*size:* " + toStr(fs_stat->size) +
                                m_eol + "*used:* " + toStr(fs_stat->used) +
                                m_eol + "*free:* " + toStr(fs_stat->avail) + m_eol;

                }
                fs_stat++;
        }
        return result;

}

const std::string AgentSocket::getLoadStat(bool is_checked)
{
        sg_load_stats *load_stat = sg_get_load_stats();
        std::string result;
        if (is_checked)
        {
                if ((load_stat->min1 > m_load_threshold) && !m_data->isHighLoadAvg())
                {
                        result = this->getHostname() + ": *CPU load average is over the threshold*:\t" + toStr(load_stat->min1)
                                + m_eol;
                        m_data->setHighLoadAvg();
                }
                else if ((load_stat->min1 < m_load_threshold) && m_data->isHighLoadAvg())
                {
                        result = this->getHostname() + ": CPU load average within norm" + m_eol;
                        m_data->setHighLoadAvg(false);
                }
        }
        else
                result = m_eol + "load average over 1 minute:\t" + toStr(load_stat->min1)
                + m_eol + "load average over 5 minutes:\t" + toStr(load_stat->min5)
                + m_eol + "load average over 15 minutes:\t" + toStr(load_stat->min15);
        return result;
}

const std::string AgentSocket::getMemStat(bool is_checked)
{
        sg_mem_stats *mem_stat = sg_get_mem_stats();
        std::string result;
        long long free_mem = mem_stat->total - mem_stat->used + mem_stat->cache,
                threshold = m_memory_threshold * 1000 * 1000;
        if (is_checked)
        {
                if ((free_mem < threshold) && !m_data->isLowFreeMem())
                {
                        result = this->getHostname() + ": *Low free memory*:\t" + toStr(free_mem) + m_eol;
                        m_data->setLowFreeMem();

                }
                else if ((free_mem > threshold) && m_data->isLowFreeMem())
                {
                        result = this->getHostname() + ": Free memory within norm\t" + m_eol;
                        m_data->setLowFreeMem(false);
                }
        }
        else
        {
                result = m_eol + "Total amount of memory:\t" + toStr(mem_stat->total)
                        + m_eol + "Total free memory:\t" + toStr(mem_stat->free)
                        + m_eol + "Total used memory:\t" + toStr(mem_stat->used)
                        + m_eol + "amount of cache used:\t" + toStr(mem_stat->cache);
        }
        return result;

}

void AgentSocket::replace(std::string &source, const std::string &substr1,
                          const std::string &substr2)
{
        uint pos = source.find(substr1), end = std::string::npos;
        while (pos != end)
        {
                source.replace(pos, substr1.length(), substr2.c_str(), substr2.length());
                pos = source.find(substr1);
        }
}